<?php
namespace app\admin\controller;

use app\BaseController;
use think\facade\Cache;
use think\Facade\Db;

class Scan extends BaseController
{
    private $ffmpeg_version;
    private $ffprobe_version;

    public function initialize()
    {
        $command = '/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffmpeg -version';
        $ffmpeg = shell_exec($command);
        $this->ffmpeg_version = $ffmpeg;

        $command = '/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffprobe -version';
        $ffprobe = shell_exec($command);
        $this->ffprobe_version = $ffprobe;
    }

    public function index()
    {
        $page = input('post.page', 1);
        $rows = input('post.rows', 20);
        $field = input('post.field', 'id');
        $order = str_replace('ending', '', input('post.order', 'desc'));
        $datas = Db::name('result')->order($field, $order)->paginate(['list_rows' => $rows, 'page' => $page]);
        return ['code' => 0, 'datas' => $datas];
    }

    public function scan()
    {
        Cache::remember('time', time());
        $config = input('post.config');
        $this->generatedAddress($config);

        if ($config['thread']) {
            $this->multiThread($config);
        } else {
            $this->singleThread($config);
        }
    }

    public function stop()
    {
        Cache::set('working', false);
    }

    public function progress()
    {
        $datas['working'] = Cache::remember('working', false);
        $datas['time'] = (int) Cache::remember('time', time());
        $datas['count'] = Db::name('address')->count();
        $datas['valid'] = Db::name('address')->where('status', 1)->count();
        $datas['finish'] = Db::name('address')->where('status', '<>', -1)->count();
        $datas['progress'] = $datas['count'] ? round(($datas['finish'] / $datas['count']) * 100) : 0;

        $elapsed = 0;

        if ($datas['progress'] != 100 && $datas['progress'] > 0) {
            $elapsed = time() - $datas['time'];
        }

        if ($datas['progress'] == 100) {
            Cache::delete('time');
        }

        $datas['elapsed'] = $elapsed;

        $data = Db::name('address')->where('status', '<>', -1)->whereNotNull('code')->order('mtime', 'DESC')->paginate(10)->toArray();
        $datas['data'] = $data['data'];
        return ['code' => 0, 'datas' => $datas];
    }

    public function clear()
    {
        $field = input('post.field');
        Db::execute("TRUNCATE `pan_" . $field . "`");
        return ['code' => 0, 'message' => '已清空！'];
    }

    protected function singleThread($config)
    {
        $datas = Db::name('address')->field('id,url')->where('status', -1)->order('id', 'ASC')->select();

        Cache::set('working', true);
        Cache::set('index', 0);
        foreach ($datas as $val) {
            $working = Cache::get('working');
            if ($working == false) {
                break;
            }

            $url = $val['url'];
            $streams = $this->getStreams($config, $url);

            //print_r($streams);die;

            $address = ['status' => 0, 'code' => $streams['code'], 'message' => null];

            if ($streams['code'] == 1 && !empty($streams['datas'])) {

                $address['status'] = 1;
                $result = $streams['datas'];

                if ($config['high'] && $result['video_height'] < 1080) {
                    Db::name('address')->where('id', $val['id'])->update($address);
                    continue;
                }

                $channel = Db::name('result')->where('url', $url)->find();

                if (!$channel) {
                    Cache::inc('index');
                    $result['name'] = '频道' . Cache::get('index');
                    $result['url'] = $url;

                    $id = Db::name('result')->insertGetId($result);
                    if ($id) {
                        $this->getCover($url, $config, $id);
                    }
                }

            } else {
                $address['message'] = $streams['message'];
            }

            Db::name('address')->where('id', $val['id'])->update($address);
        }
        $this->stop();
    }

    protected function multiThread($config)
    {
        $data = Db::name('address')->field('id,url')->where('status', -1)->order('id', 'ASC')->find();
        if (empty($data)) {
            return;
        }


        Db::name('address')->where('id', $data['id'])->update(['status' => 0]);

        $url = $data['url'];
        $streams = $this->getStreams($config, $url);

        //print_r($streams);die;

        $address = ['status' => 0, 'code' => $streams['code'], 'message' => null];

        if ($streams['code'] == 1 && !empty($streams['datas'])) {

            $address['status'] = 1;
            $result = $streams['datas'];

            if ($config['high'] && $result['video_height'] < 1080) {
                Db::name('address')->where('id', $data['id'])->update($address);
                return;
            }

            $channel = Db::name('result')->where('url', $url)->find();

            if (!$channel) {
                Cache::inc('index');
                $result['name'] = '频道' . $data['id'];
                $result['url'] = $url;

                $id = Db::name('result')->insertGetId($result);
                if ($id) {
                    $this->getCover($url, $config, $id);
                }
            }

        } else {
            $address['message'] = $streams['message'];
        }

        Db::name('address')->where('id', $data['id'])->update($address);
    }

    protected function generatedAddress($config)
    {

        $count = Db::name('address')->count();
        if ($count) {
            return;
        }

        preg_match_all('/\{(.*?)\}/', $config['url'], $match);

        //print_r($match);die;

        if (!count($match[0])) {
            return [
                'code' => 1,
                'message' => '模版变量不正确！终止搜索！',
            ];
        }

        $complex = -1;

        foreach ($match[1] as $key => $value) {
            if (strpos($value, '/') !== false) {
                $number = explode('/', $value);
                if ($complex == $number[0]) {
                    break;
                }
                $complex = $number[0];
                //print_r($number);die;
                $number = $number[1];
                $number = explode('-', $number);
            } else {
                $number = explode('-', $value);
            }

            //print_r($number);die;
            $start = $number[0];
            $end = $number[1];
            if ($end <= $start) {
                return [
                    'code' => 1,
                    'message' => '模版变量不正确！终止搜索！',
                ];
            }

            $field['start'] = $start;
            $field['end'] = $end;
            $field['count'] = $end - $start + 1;
            $field['number'] = $start;
            $data[] = $field;
        }

        $count = array_product(array_column($data, 'count'));
        $index = 0;
        $end = count($data) - 1;
        while ($index < $count) {
            ++$index;
            $channel = $config['url'];
            foreach ($data as $key => $value) {
                $str = $match[0][$key];
                //$str = preg_quote($str, '/');
                //print_r($str);die;
                $number = $value['number'];
                if ($complex != -1) {
                    $channel = str_replace($str, $number, $channel);
                } else {
                    $channel = preg_replace("/$str/", $number, $channel, 1);
                }

                //print_r($channel);die;
                if ($key == $end) {
                    if ($value['number'] == $value['end']) {
                        $data = $this->addCount($data, $key);
                        $data[$key]['number'] = $data[$key]['start'];
                    } else {
                        $len = strlen($data[$key]['number']);
                        $data[$end]['number'] = $this->dispRepair(++$data[$end]['number'], $len);
                    }
                }
            }

            $fields[] = ['url' => $channel];

        }

        $res = Db::name('address')->insertAll($fields);

        if (!$res) {
            return ['code' => 1, 'message' => '地址生成失败！'];
        }
    }

    protected function dispRepair($str, $len, $msg = '0', $type = '1')
    {
        $length = $len - strlen($str);
        if ($length < 1) {
            return $str;
        }
        if ($type == 1) {
            $str = str_repeat($msg, $length) . $str;
        } else {
            $str .= str_repeat($msg, $length);
        }
        return $str;
    }

    protected function addCount($data, $key)
    {
        $i = $key - 1;
        if ($i < 0) {
            return $data;
        }
        if ($data[$i]['number'] == $data[$i]['end']) {
            $data = $this->addCount($data, $i);
            $data[$i]['number'] = $data[$i]['start'];
        } else {
            $len = strlen($data[$i]['number']);
            $data[$i]['number'] = $this->dispRepair(++$data[$i]['number'], $len);
        }
        return $data;
    }

    protected function getStreams($config, $channel)
    {
        $command = $this->ffprobeInfo($channel, $config);
        //print_r($command);die;
        $json = shell_exec($command);
        //print_r($json);die;
        $media = json_decode($json, true);
        if (empty($media)) {
            return ['code' => -1, 'message' => '内容空！'];
        } elseif (isset($media['error'])) {
            $error = $media['error'];
            return ['code' => $error['code'], 'message' => $error['string']];
        } elseif (isset($media['streams']) && !empty($media['streams'])) {
            $field = [];
            foreach ($media['streams'] as $value) {
                if (!isset($value['codec_type'])) {
                    continue;
                }

                if ($value['codec_type'] == 'video') {
                    $field['video_width'] = $value['width'];
                    $field['video_height'] = $value['height'];
                    $field['video_encoded'] = $value['codec_name'];
                    $field['video_frame'] = str_replace('/1', '', $value['r_frame_rate']);
                }

                if ($value['codec_type'] == 'audio' && $value['channels']) {
                    $field['audio_encoded'] = $value['codec_name'];
                    $field['audio_track'] = $value['channel_layout'];
                    $field['audio_sampling_rate'] = $value['sample_rate'];
                }
            }
            return ['code' => 1, 'datas' => $field];
        }
        return ['code' => -1, 'message' => '内容空！'];
    }

    protected function getCover($channel, $config, $id)
    {
        $path = public_path() . 'storage/cover/' . $id . '.jpg';
        if (is_file($path)) {
            unlink($path);
        }

        $command = $this->ffmpegCover($channel, $config, $path);
        //print_r($command);die;
        shell_exec($command);
    }

    public function rate()
    {
        $res = Db::name('result')->select();
        $command = '/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffmpeg -re -y -i "%s" -c:v copy -t 00:00:10 -f mp4 /dev/null 2>&1';
        foreach ($res as $value) {
            //ffmpegBiterate();
            $json = shell_exec(sprintf($command, $value['url']));
            preg_match_all('/bitrate=(\d+\.\d+)kbits/', $json, $match);
            //print_r($json);die;
            if (!count($match[1])) {
                continue;
            }
            $arr = array_slice($match[1], -5);
            $sum = array_sum($arr);
            $num = round($sum / 5 / 1024);
            Db::name('result')->where('id', $value['id'])->update(['video_rate' => $num]);
        }
    }

    protected function ffprobeInfo($channel, $config)
    {
        $command = [];
        $merge = ['/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffprobe'];

        if (!empty($config['proxy'])) {
            $command['-http_proxy'] = $config['proxy'];
        }

        if (!empty($config['timeout'])) {
            if (strpos($channel, 'rtmp') === false) {
                $command['-timeout'] = $config['timeout'] * 1000000;
            } else {
                $command['-rw_timeout'] = $config['timeout'] * 1000000;
            }
        }

        if (strpos($channel, 'rtsp') !== false) {
            $command['-rtsp_transport'] = 'tcp';
        }

        if (!empty($config['agent'])) {
            $command['-user_agent'] = '"' . $config['agent'] . '"';
        }

        $command['-v'] = 'quiet';
        $command['-show_format'] = '';
        $command['-show_streams'] = '';
        $command['-show_error'] = '';
        $command['-print_format'] = 'json';
        $command['-i'] = '"' . $channel . '"';
        $command['2>&1'] = '';

        return $this->createCommand($command, $merge);
    }

    protected function ffmpegCover($channel, $config, $path)
    {
        $command = [];
        $merge = ['/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffmpeg'];

        if (!empty($config['proxy'])) {
            $command['-http_proxy'] = $config['proxy'];
        }

        if (strpos($channel, 'rtsp') !== false) {
            $command['-rtsp_transport'] = 'tcp';
        }

        if (!empty($config['userAgent'])) {
            $command['-user_agent'] = '"' . $config['userAgent'] . '"';
        }

        $command['-i'] = '"' . $channel . '"';
        $command['-y'] = '';
        $command['-f'] = 'image2';
        $command['-ss'] = 1;
        $command['-vframes'] = 1;
        $command[$path] = '';
        return $this->createCommand($command, $merge);
    }

    protected function ffmpegRate($channel, $config)
    {
        $command = [];
        $merge = ['/Applications/XAMPP/xamppfiles/htdocs/ffmpeg/ffmpeg'];

        if (!empty($config['proxy'])) {
            $command['-http_proxy'] = $config['proxy'];
        }

        if (strpos($channel, 'rtsp') !== false) {
            $command['-rtsp_transport'] = $config['tcp'];
        }

        if (!empty($config['userAgent'])) {
            $command['-user_agent'] = '"' . $config['userAgent'] . '"';
        }

        return $this->createCommand($command, $merge);
    }

    protected function createCommand($command, $merge)
    {
        foreach ($command as $key => $val) {
            $merge[] = $key;
            if (!empty($val)) {
                $merge[] = $val;
            }
        }
        return implode(' ', $merge);
    }

}
